package com.jinit.jia.boot;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.*;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletRegistration;
import java.lang.reflect.Constructor;
import java.util.*;

/**
 * SpringApplication
 *
 * @author JInit
 * @date 2021-08-04
 */
public class SpringApplication {

    private static final Log logger = LogFactory.getLog(SpringApplication.class);

    /**
     * 默认情况下将用于 web 环境的应用程序上下文的类名
     */
    public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "com.jinit.jia.boot.web.servlet.context." +
            "ServletWebServerApplicationContext";

    /**
     * 资源加载器
     */
    private ResourceLoader resourceLoader;

    /**
     * 应用上下文初始化对象集合
     */
    private List<ApplicationContextInitializer<?>> initializers;

    /**
     * 应用监听器
     */
    private List<ApplicationListener<?>> listeners;

    /**
     * main方法的class
     */
    private Class<?> mainApplicationClass;

    /**
     * web应用类型
     */
    private WebApplicationType webApplicationType;

    /**
     * 应用上下文
     */
    private Class<? extends ConfigurableApplicationContext> applicationContextClass;

    /**
     * 主bean资源集合
     */
    private Set<Class<?>> primarySources;

    /**
     * SpringApplication 静态运行入口
     * @param primarySource 主要的资源
     * @param args 应用参数（通常从java main方法传递）
     * @return
     */
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    /**
     * SpringApplication 静态运行入口
     * @param primarySources 主要的资源 数组
     * @param args 应用参数（通常从java main方法传递）
     * @return
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        // 实例化SpringApplication，调用run方法
        return new SpringApplication(primarySources).run(args);
    }

    /**
     * 构造方法
     * @param primarySources 主bean资源
     */
    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }

    /**
     * 构造方法
     * @param resourceLoader 资源加载器
     * @param primarySources 主bean资源
     */
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 设置资源加载器
        this.resourceLoader = resourceLoader;
        // 断言主bean资源不能为null
        Assert.notNull(primarySources, "PrimarySources不能为空");
        // 将primarySources数组转换为List，最后放到LinkedHashSet集合中
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

        // 1、推断应用类型，后面会根据类型初始化对应的环境
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 2、初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 3、初始化classpath下所有已配置的应用监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 4、根据调用栈，推断出 main 方法的类名
        this.mainApplicationClass = deduceMainApplicationClass();
    }

    /**
     * 推断出Main方法所在类Class
     * @return
     */
    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // 继续查找
        }
        return null;
    }

    /**
     * 设置初始化启动器集合
     * @param initializers 初始化启动器集合
     */
    public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
        this.initializers = new ArrayList<>(initializers);
    }

    /**
     * 设置应用监听器集合
     * @param listeners 监听器集合
     */
    public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
        this.listeners = new ArrayList<>(listeners);
    }

    /**
     * 获取spring工厂实例
     * @param type class 类型
     * @param <T>
     * @return
     */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    /**
     * 获取spring工厂实例
     * @param type class 类型
     * @param parameterTypes 参数类型数组
     * @param args 可变参数
     * @param <T>
     * @return
     */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // 通过指定的classLoader从 META-INF/spring.factories 的资源文件中，
        // 读取 key为type.getName()的value
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 创建工厂实例
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 对Spring工厂实例排序（org.springframework.core.annotation.Order注解指定的顺序）
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

    /**
     * 创建spring工厂实例
     * @param type class类型
     * @param parameterTypes 参数类型数组
     * @param classLoader 类加载器
     * @param args 参数数组
     * @param names 待实例化工厂全限定类名
     * @param <T>
     * @return
     */
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
                                                       ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }

    /**
     * 获取类加载器
     * @return
     */
    public ClassLoader getClassLoader() {
        if (this.resourceLoader != null) {
            return this.resourceLoader.getClassLoader();
        }
        return ClassUtils.getDefaultClassLoader();
    }

    /**
     * SpringApplication 运行入口
     * @param args 应用参数（通常从java main方法传递）
     * @return
     */
    public ConfigurableApplicationContext run(String... args) {
        // 记录程序运行时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // ConfigurableApplicationContext Spring 的上下文
        ConfigurableApplicationContext context = null;
        // Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 设置该应用程序,即使没有检测到显示器,也允许其启动.
        // configureHeadlessProperty();
        //1、获取并启动监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            //ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 2、构造应用上下文环境
            //ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            // 处理需要忽略的Bean
            //configureIgnoreBeanInfo(environment);
            // 打印banner
            //Banner printedBanner = printBanner(environment);
            // 3、初始化应用上下文
            context = createApplicationContext();
            // 实例化SpringBootExceptionReporter.class，用来支持报告关于启动的错误
//            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
//                    new Class[] { ConfigurableApplicationContext.class }, context);
            // 4、刷新应用上下文前的准备阶段
            prepareContext(context, listeners);
            // 5、刷新应用上下文
            refreshContext(context);

            // 6、刷新应用上下文后的扩展接口
            //afterRefresh(context, applicationArguments);
            // 时间记录停止
            stopWatch.stop();
            logger.info("启动耗时：" + stopWatch.getTotalTimeMillis() + "ms");
            // 发布容器启动完成事件
            listeners.started(context);
            // callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            // handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            // handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

    /**
     * 获取运行监听器
     * @param args 参数
     * @return
     */
    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        // SpringApplicationRunListeners
        return new SpringApplicationRunListeners(logger,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

    /**
     * 创建应用上下文环境
     * @return
     */
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                        break;
                    default:
                        // 暂不实现其他类型
                        contextClass = Class.forName("");
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException("无法创建默认应用上下文，请指定应用上下文类 ", ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

    /**
     * 准备上下文
     * @param context
     * @param listeners
     */
    private void prepareContext(ConfigurableApplicationContext context,
                                SpringApplicationRunListeners listeners) {
        // 设置容器环境
        //context.setEnvironment(environment);
        // 执行容器后置处理
        //postProcessApplicationContext(context);
        // applyInitializers(context);
        // 向各个监听器发送容器已经准备好的事件
        listeners.contextPrepared(context);
//        if (this.logStartupInfo) {
//            logStartupInfo(context.getParent() == null);
//            logStartupProfileInfo(context);
//        }
//        // Add boot specific singleton beans
//        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//        //将main函数中的args参数封装成单例Bean，注册进容器
//        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
//        if (printedBanner != null) {
//            //将 printedBanner 也封装成单例，注册进容器
//            beanFactory.registerSingleton("springBootBanner", printedBanner);
//        }
//        if (beanFactory instanceof DefaultListableBeanFactory) {
//            ((DefaultListableBeanFactory) beanFactory)
//                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
//        }
//        if (this.lazyInitialization) {
//            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
//        }
        // 加载资源
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "资源不能为空");
        // 加载启动类，将启动类注入容器
        load(context, sources.toArray(new Object[0]));
        // 发布容器已加载事件
        listeners.contextLoaded(context);
    }

    /**
     * 将 bean 加载到应用程序上下文
     * @param context
     * @param sources
     */
    protected void load(ApplicationContext context, Object[] sources) {
        BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
        loader.load();
    }

    protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
        return new BeanDefinitionLoader(registry, sources);
    }

    /**
     * 获取bean定义注册表
     * @param context
     * @return
     */
    private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
        if (context instanceof BeanDefinitionRegistry) {
            return (BeanDefinitionRegistry) context;
        }
        if (context instanceof AbstractApplicationContext) {
            return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
        }
        throw new IllegalStateException("找不到 bean 定义注册表");
    }

    /**
     * 获取所有资源
     * @return
     */
    public Set<Object> getAllSources() {
        Set<Object> allSources = new LinkedHashSet<>();
        if (!CollectionUtils.isEmpty(this.primarySources)) {
            allSources.addAll(this.primarySources);
        }
        return Collections.unmodifiableSet(allSources);
    }

    /**
     * 刷新上下文
     * @param context 上下文
     */
    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
//        if (this.registerShutdownHook) {
//            try {
//                context.registerShutdownHook();
//            }
//            catch (AccessControlException ex) {
//                // Not allowed in some environments.
//            }
//        }
    }

    /**
     * 刷新上下文
     * @param applicationContext 应用上下文
     */
    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }


}
